package com.hevs.samplewebapp.client.screens;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.SimplePager.Resources;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.CalendarUtil;
import com.google.gwt.user.datepicker.client.DateBox;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.VisualizationUtils;
import com.google.gwt.visualization.client.visualizations.LineChart;
import com.google.gwt.visualization.client.visualizations.LineChart.Options;
import com.hevs.samplewebapp.client.bean.CompositeWRefresh;
import com.hevs.samplewebapp.client.bean.MedicalDataItem;
import com.hevs.samplewebapp.client.bean.TimeLaps;
import com.hevs.samplewebapp.client.bean.Type;
import com.hevs.samplewebapp.client.bean.User;
import com.hevs.samplewebapp.client.services.PatientManagementServiceImpl;


/**
 * Widget to display graphical representation for the doctor
 * <p>It provides the doctor the graphical representation of medical data for a choosen patient and for a choosen time laps</p>
 * @author Matthias Hurni
 * @Date: 20.04.2012 12:41:28
 * 
 */
public class DoctorGraphWidget extends CompositeWRefresh
{
	// ------------------------------------------------------------
	// VARIABLES
	// ------------------------------------------------------------
	/**
	 * Horizontal panel contains the widget's elements    
	 */
	private HorizontalPanel mainPanel=new HorizontalPanel();

	/**
	 * Horizontal panel with the different time laps
	 */
	private HorizontalPanel timeLapPanel=new HorizontalPanel();

	/**
	 * Horizontal panel with the range of date    
	 */
	private HorizontalPanel dateDisplayPanel=new HorizontalPanel();

	/**
	 * Vertical panel with the graphic in it
	 */
	private VerticalPanel graphPanel=new VerticalPanel();

	/**
	 * Listbox of types of medical data chosen to be display
	 */
	private ListBox types=new ListBox();

	/**
	 * Button to display the today medical data
	 */
	private Button dayButton=new Button(TimeLaps.get(0).getName());

	/**
	 * Button to display the weekly medical data, last seven days
	 */
	private Button weekButton=new Button(TimeLaps.get(1).getName());

	/**
	 * Button to display the monthly medical data, last 30 days
	 */
	private Button monthButton=new Button(TimeLaps.get(2).getName());

	/**
	 * Button to display the quarterly medical data, last 90 days 
	 */
	private Button threeMonthsButton=new Button(TimeLaps.get(3).getName());

	/**
	 * Button do display a date picker to choose a custom range of date
	 */
	private Button customButton=new Button(TimeLaps.get(4).getName());

	/**
	 * Label to display the beginning date
	 */
	private Label fromLabel=new Label();

	/**
	 * Label to display the ending date
	 */
	private Label toLabel=new Label();

	/**
	 * Date of beginning
	 */
	private Date fromDate=null;

	/**
	 * Date of ending 
	 */
	private Date toDate=null;

	/**
	 * Formatter for the date
	 */
	private static final DateTimeFormat dateFormat=DateTimeFormat.getFormat("dd.MM.yyyy");

	/**
	 * Chart in line to show the evolution of medical data
	 */
	private LineChart chart;

	/**
	 * List with the medical data of a specific patient
	 * @see ListDataProvider
	 */
	private ListDataProvider<User> patientDataProvider;

	/**
	 * List of patients how granted this doctor    
	 */
	private List<User> patients;

	/**
	 * CellTable that contains the List of patient
	 */
	private CellTable<User> patientsTable;

	/**
	 * Column contains the firstname of the patients
	 */
	private TextColumn<User> firstNameColumn;

	/**
	 * Column contains the lastname of the patients
	 */
	private TextColumn<User> lastNameColumn;

	/**
	 * Handler to sort the List of patients by firstname
	 */
	private ListHandler<User> firstNameSortHandler;

	/**
	 * Handler to sort the List of patients by lastname
	 */
	private ListHandler<User> lastNameSortHandler;

	/**
	 * Pager to go trough the list of patient when they are above 15
	 */
	private SimplePager patientsPager;

	/**
	 * ClientBundle that provides pager images for this widget   
	 */
	private Resources resources=GWT.create(SimplePager.Resources.class); 

	/**
	 * The patient selected from how the doctor want to display medical data graphical representation
	 */
	private User currentPatient;

	/**
	 * List of patient from the Web service
	 */
	private List<User> patientListRest;

	/**
	 * List of medical data from the Web service
	 */
	private List<MedicalDataItem> medDatListRest;

	/**
	 * Initialization of the web service for graphical representation of medical data for the doctor
	 */
	private static PatientManagementServiceImpl docGraphService=new PatientManagementServiceImpl();

	// ------------------------------------------------------------
	// CONSTRUCTOR
	// ------------------------------------------------------------   
	/**
	 * Constructor of the Doctor Graph Widget 
	 */
	public DoctorGraphWidget(){
		// ------------------------------------------------------------------------
		// Layout description
		// ------------------------------------------------------------------------
		setListBoxListeners();
		setButtonListeners();
		mainPanel.setSpacing(5);
		mainPanel.add(patientList());
		mainPanel.add(graphForm());

		// ------------------------------------------------------------------------
		// Widget initialization
		// ------------------------------------------------------------------------
		initWidget(mainPanel);
	}

	// ------------------------------------------------------------------------
	// METHODS
	// ------------------------------------------------------------------------

	/**
	 * Method to create the graph form
	 * @return
	 * a widget with the graph in it
	 */
	private Widget graphForm()
	{
		// Fill the ListBox with defined Types
		for(int i=0;i<Type.getSize();i++)
		{
			Type temp=Type.getFromInt(i);
			types.addItem(temp.getName(),String.valueOf(temp.getLevel()));
		}

		fromLabel.setWidth("125px");
		toLabel.setWidth("125px");

		dateDisplayPanel.setSpacing(5);
		dateDisplayPanel.add(fromLabel);
		dateDisplayPanel.add(toLabel);

		graphPanel.setSpacing(5);
		graphPanel.add(timeRangeForm());
		graphPanel.add(dateDisplayPanel);
		graphPanel.add(types);

		// Load the visualization api, passing the onLoadCallback to be called when loading is done
		VisualizationUtils.loadVisualizationApi(createGraph(),LineChart.PACKAGE);

		return graphPanel;
	}

	/**
	 * Method to create the time range tab
	 * @return
	 * a widget with the different time lap in it
	 */
	private Widget timeRangeForm()
	{
		dayButton.setStyleName("gwt-ButtonRangeTab");
		weekButton.setStyleName("gwt-ButtonRangeTab");
		monthButton.setStyleName("gwt-ButtonRangeTab");
		threeMonthsButton.setStyleName("gwt-ButtonRangeTab");
		customButton.setStyleName("gwt-ButtonRangeTab");

		timeLapPanel.add(dayButton);
		timeLapPanel.add(weekButton);
		timeLapPanel.add(monthButton);
		timeLapPanel.add(threeMonthsButton);
		timeLapPanel.add(customButton);

		// Set default time range (one week)
		setDateRange(7,false);

		return timeLapPanel;
	}

	/**
	 * Method to create the cell table for patients
	 * @return
	 * a widget with the list of granted patients in it
	 */
	private Widget patientList()
	{
		VerticalPanel vPanel=new VerticalPanel();

		patientsTable=new CellTable<User>();
		patientListRest=new ArrayList<User>();

		// ------------------------------------------------------------------------
		// Cell table with patients' names
		// ------------------------------------------------------------------------

		// Get the list of patients into the table content provider
		patientDataProvider=new ListDataProvider<User>();

		// Rest call to get a list of patients
		patientListRest=getPatientList();

		// Create the pager for the table
		patientsPager=new SimplePager(TextLocation.CENTER,resources,false,1000,true);
		patientsPager.setDisplay(patientsTable);
		patientsPager.setPageSize(10);

		// Create the first name header of column
		firstNameColumn=new TextColumn<User>()
				{
			@Override
			public String getValue(User user)
			{
				return user.getFirstName();
			}
				};

				// Create the last name header of column
				lastNameColumn=new TextColumn<User>()
				{
					@Override
					public String getValue(User user)
					{
						return user.getLastName();
					}
				};

				// Make the relevant columns sortable
				firstNameColumn.setSortable(true);
				lastNameColumn.setSortable(true);

				// Add the columns to the cell table
				patientsTable.addColumn(firstNameColumn,"Fist name");
				patientsTable.addColumn(lastNameColumn,"Last name");

				// Set columns sizes
				patientsTable.setColumnWidth(firstNameColumn,25.0,Unit.PCT);
				patientsTable.setColumnWidth(lastNameColumn,25.0,Unit.PCT);

				// Add a selection model to handle user selection
				final SingleSelectionModel<User> selectionModel=new SingleSelectionModel<User>();
				patientsTable.setSelectionModel(selectionModel);
				selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler()
				{
					public void onSelectionChange(SelectionChangeEvent event)
					{
						User selected=selectionModel.getSelectedObject();
						if(selected!=null)
						{
							// Set current patient
							currentPatient=selected;

							// Get medical data of current patient
							medDatListRest=getMedDatList();

							// Redraw graph with new data (set default enum)
							types.setSelectedIndex(0);
							refreshGraph(Type.getFromVal(0));
						}
					}
				});

				// Set content of the table from the patients in the list
				patientDataProvider.addDataDisplay(patientsTable);

				vPanel.add(patientsTable);
				vPanel.add(patientsPager);
				vPanel.setCellHorizontalAlignment(patientsPager,VerticalPanel.ALIGN_CENTER);

				return vPanel;
	}

	/**
	 * Method to return a sub array with only required medical data
	 * @param fromDate
	 * date of beginning
	 * @param toDate
	 * date of end
	 * @param list
	 * List of the patient's medical data
	 * @return
	 * the list of medical data between the chosen range for a patient
	 */
	private List<MedicalDataItem> trimByDate(Date fromDate,Date toDate,List<MedicalDataItem> list)
	{
		// Variables
		List<MedicalDataItem> trimmedData=new ArrayList<MedicalDataItem>();
		DateTimeFormat dateFormat=DateTimeFormat.getFormat("dd.MM.yyyy");

		// Go through all the medical data and check if date is between our two reference dates
		for(MedicalDataItem data:list)
		{
			if(data!=null)
			{
				Date dataDate=dateFormat.parse(data.getDate()); 
				if(dataDate.compareTo(toDate)<=0&&dataDate.compareTo(fromDate)>=0)
					trimmedData.add(data);
			}
		}       
		return trimmedData;
	}

	/**
	 * Method to return a sub array with only the corresponding type
	 * @param type
	 * the chosen type to be displayed in the graph
	 * @return
	 * the List of medical data for the specified type
	 */
	private List<MedicalDataItem> trimByType(Type type)
	{
		// Variables
		List<MedicalDataItem> trimmedData=new ArrayList<MedicalDataItem>();

		// Go through all the medical data and filter it by type
		if(medDatListRest!=null)
			for(MedicalDataItem data:medDatListRest)
				if(data!=null&type.compareTo(Type.getFromVal(data.getType()))==0)
					trimmedData.add(data);
		return trimmedData;
	}

	/**
	 * Method to initiate the graph
	 * @return
	 * a runnable line chart
	 */
	private Runnable createGraph()
	{
		Runnable onLoadCallback=new Runnable()
		{
			public void run()
			{
				chart=new LineChart(null,createOptions("eHealth graph"));
				graphPanel.add(chart);
			}
		};
		return onLoadCallback;
	}

	/**
	 * Method to create Graph options
	 * @param title
	 * title of the options 
	 * @return
	 * an option component for graph
	 */
	private Options createOptions(String title)
	{
		Options options = Options.create();
		options.setWidth(510);
		options.setHeight(300);
		options.setTitle(title);
		return options;
	}

	/**
	 * Method to refresh the patients' cell table
	 */
	public void refreshPatientCellTable()
	{
		// Rest call to get a list of patients
		patientsTable.setRowCount(0);
		patientListRest=getPatientList();

		setPatientListListeners();
		patientsTable.setRowCount(patientListRest.size(),true);

		// Reset current patient
		currentPatient=null;
	}

	/**
	 * Method to sort the data by date 
	 * @param list
	 * List of medical data to be sorted
	 */
	private void sortByDate(List<MedicalDataItem> list)
	{
		Collections.sort(list,new Comparator<MedicalDataItem>()
				{
			@Override
			public int compare(MedicalDataItem o1,MedicalDataItem o2)
			{
				Date d1;
				Date d2;

				if(o1==o2)
					return 0;

				d1=dateFormat.parse(o1.getDate());
				d2=dateFormat.parse(o2.getDate());

				// Compare the dates
				if(d1!=null)
					return (d2!=null)?d1.compareTo(d2):1;
					return -1;
			}
				});
	}

	/**
	 * Method to set a fix date range
	 * @param days
	 * number of days in the range
	 * @param redraw
	 * need to reedraw the graph
	 */
	private void setDateRange(int days,boolean redraw)
	{
		toDate=new Date();
		fromDate=dateFormat.parse(dateFormat.format(new Date())); // To remove the hours,minutes and seconds
		CalendarUtil.addDaysToDate(fromDate,days*-1);
		setDateRangeLabels();
		if(redraw)
			refreshGraph(Type.getFromInt(types.getSelectedIndex()));
	}

	/**
	 * Method to set the current dates on the labels
	 */
	private void setDateRangeLabels()
	{
		if(fromDate!=null&&toDate!=null)
		{
			fromLabel.setText("From: "+dateFormat.format(fromDate));
			toLabel.setText("To: "+dateFormat.format(toDate));
		}
		else
		{
			fromLabel.setText("From: -");
			toLabel.setText("To: -");
		}
	}

	/**
	 * Method to get the patient list form server
	 * @return
	 * the List of patient granted for this doctor
	 */
	private List<User> getPatientList()
	{
		docGraphService.getPatients();
		return docGraphService.getPatientList();
	}

	/**
	 * Method to get the medical data of the current patient
	 * @return
	 * list of medical data of a granted patient
	 */
	private List<MedicalDataItem> getMedDatList()
	{
		docGraphService.getMedDats(currentPatient.getEmail(), DoctorGraphWidget.this);
		return docGraphService.getMedDatList();
	}

	/**
	 * Method to create the DataTable for the graph
	 * @param type
	 * the type of medical data to display in the graph
	 */
	public void refreshGraph(final Type type)
	{
		// Variables
		DataTable data;
		List<MedicalDataItem> trimmedData;

		// Set proper ListBox Item
		types.setSelectedIndex(Type.getIndex(type.getLevel()));

		// Prepare the data
		trimmedData=trimByType(type); // Filter for only one type
		if(fromDate!=null&toDate!=null) // Filter dates out of range (if set)
			trimmedData=trimByDate(fromDate,toDate,trimmedData);
		sortByDate(trimmedData); // Sort the array by date

		data=DataTable.create();
		data.addColumn(ColumnType.STRING,"Date");
		data.addColumn(ColumnType.NUMBER,"Value");
		data.addRows(trimmedData.size());
		for(int i=0;i<trimmedData.size();i++)
		{
			MedicalDataItem temp=trimmedData.get(i);
			data.setValue(i,0,temp.getDate());
			data.setValue(i,1,temp.getValue());
		}
		chart.draw(data,createOptions(type.getName()));
	}

	// ------------------------------------------------------------------------
	// Listeners methods
	// ------------------------------------------------------------------------ 

	/**
	 * Listener to define the chosen type from the Listbox
	 */
	private void setListBoxListeners()
	{
		types.addChangeHandler(new ChangeHandler()
		{
			@Override
			public void onChange(ChangeEvent event)
			{
				int index=types.getSelectedIndex();
				if(index!=-1)
					refreshGraph(Type.getFromInt(types.getSelectedIndex()));
			}
		});
	}

	/**
	 * Button listener to change the range of date
	 */
	private void setButtonListeners()
	{
		dayButton.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				setDateRange(0,true);
			}
		});
		weekButton.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				setDateRange(7,true);
			}
		});
		monthButton.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				setDateRange(30,true);
			}
		});
		threeMonthsButton.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				setDateRange(90,true);
			}
		});
		customButton.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				pickDates();
			}
		});


	}

	/**
	 * Common method to update the cell table completely
	 */
	private void setPatientListListeners()
	{
		// Add the patients to the data provider (filling the GUI table)
		patients=patientDataProvider.getList();
		patients.clear();
		for(User patient:patientListRest)
			patients.add(patient);

		// ------------------------------------------------------------------------
		// Cell table's columns sorters
		// ------------------------------------------------------------------------

		// First name sorter listener
		firstNameSortHandler=new ListHandler<User>(patients);
		firstNameSortHandler.setComparator(firstNameColumn,
				new Comparator<User>()
				{
			public int compare(User o1,User o2)
			{
				if(o1==o2)
					return 0;

				// Compare the name columns
				if(o1!=null)
					return (o2!=null)?o1.getFirstName().compareTo(o2.getFirstName()):1;
					return -1;
			}
				});

		// Last name sorter listener
		lastNameSortHandler=new ListHandler<User>(patients);
		lastNameSortHandler.setComparator(lastNameColumn,
				new Comparator<User>()
				{
			public int compare(User o1,User o2)
			{
				if(o1==o2)
					return 0;

				// Compare the name columns
				if(o1!=null)
					return (o2!=null)?o1.getLastName().compareTo(o2.getLastName()):1;
					return -1;
			}
				});

		patientsTable.addColumnSortHandler(firstNameSortHandler);
		patientsTable.addColumnSortHandler(lastNameSortHandler);

		// We know that the data is sorted alphabetically by default
		patientsTable.getColumnSortList().push(firstNameColumn);
		patientsTable.getColumnSortList().push(lastNameColumn);
	}

	// ------------------------------------------------------------------------
	// DatePicker PopUp method
	// ------------------------------------------------------------------------

	/**
	 * Method to create and display a pop-up with the custom date selection
	 */
	private void pickDates()
	{
		final DialogBox box=new DialogBox();
		box.setGlassEnabled(true);
		box.setAnimationEnabled(true);
		box.setText("Choose a date range");

		// Layout of the content
		VerticalPanel mainLayout=new VerticalPanel();
		HorizontalPanel buttonsLayout=new HorizontalPanel();

		Button apply=new Button("Apply");
		Button reset=new Button("Reset");
		Button cancel=new Button("Cancel");

		DateTimeFormat dateFormat=DateTimeFormat.getFormat("dd.MM.yyyy");

		final DateBox fromBox=new DateBox();
		fromBox.setFormat(new DateBox.DefaultFormat(dateFormat));
		if(fromDate==null)
			fromBox.setValue(new Date());
		else
			fromBox.setValue(fromDate);
		fromBox.getDatePicker().setCurrentMonth(fromBox.getValue());

		final DateBox toBox=new DateBox();
		toBox.setFormat(new DateBox.DefaultFormat(dateFormat));
		if(toDate==null)
			toBox.setValue(new Date());
		else
			toBox.setValue(toDate);
		toBox.getDatePicker().setCurrentMonth(toBox.getValue());

		FlexTable tableLayout=new FlexTable();
		tableLayout.setHTML(1,0,"From:");
		tableLayout.setWidget(1,1,fromBox);
		tableLayout.setHTML(2,0,"To:");
		tableLayout.setWidget(2,1,toBox);

		apply.setWidth("100%");
		reset.setWidth("100%");
		cancel.setWidth("100%");

		// Listeners
		apply.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				fromDate=fromBox.getValue();
				toDate=toBox.getValue();
				setDateRangeLabels();
				refreshGraph(Type.getFromInt(types.getSelectedIndex()));
				box.hide();
			}                       
		});

		reset.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				fromDate=null;
				toDate=null;
				fromBox.setValue(new Date());
				fromBox.getDatePicker().setCurrentMonth(fromBox.getValue());
				toBox.setValue(new Date());
				toBox.getDatePicker().setCurrentMonth(toBox.getValue());
				setDateRangeLabels();
				refreshGraph(Type.getFromInt(types.getSelectedIndex()));
				box.hide();
			}                       
		});

		cancel.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				box.hide();
			}
		});

		buttonsLayout.add(apply);
		buttonsLayout.add(reset);
		buttonsLayout.add(cancel);
		buttonsLayout.setSpacing(10);

		mainLayout.add(tableLayout);
		mainLayout.add(buttonsLayout);
		mainLayout.setSpacing(5);
		mainLayout.setCellHorizontalAlignment(buttonsLayout,VerticalPanel.ALIGN_CENTER);

		box.setWidget(mainLayout);
		box.show();
		box.center();
	}

	// ------------------------------------------------------------------------
	// Getters - Setters
	// ------------------------------------------------------------------------

	/**
	 * Getter for the Types Listbox
	 * @return
	 * a Listbox with the types in it
	 */
	public ListBox getTypes()
	{
		return types;
	}

	/**
	 * Setter for the Types Listbox
	 * @param types
	 * the Listbox of types
	 */
	public void setTypes(ListBox types)
	{
		this.types=types;
	}

	@Override
	/**
	 * Method to refresh a component
	 */
	public void refreshComp() {	}
}